package org.dreamwork.jasmine2.web.controls;

import org.apache.log4j.Logger;
/*
import org.dreamwork.i18n.IResourceAdapter;
import org.dreamwork.i18n.IResourceManager;
*/
import org.dreamwork.i18n.LocaleWarp;
import org.dreamwork.jasmine2.engine.*;
import org.dreamwork.jasmine2.events.EventException;
import org.dreamwork.jasmine2.events.ILoadListener;
import org.dreamwork.jasmine2.events.PostbackDelegate;
import org.dreamwork.jasmine2.jxd.ClientScriptManager;
import org.dreamwork.jasmine2.util.DataContainer;
import org.dreamwork.jasmine2.util.WebControlCollection;
import org.dreamwork.jasmine2.web.IWebContainer;
import org.dreamwork.jasmine2.web.IWebControl;
import org.dreamwork.jasmine2.web.controls.layout.ReplaceHolderControl;
import org.dreamwork.jasmine2.web.html.GenericHtmlControl;
//import org.dreamwork.misc.BASE64Zipper;
import org.dreamwork.misc.Base64;
import org.dreamwork.peony.ExpressionRunner;
import org.dreamwork.util.ICollection;
import org.dreamwork.util.NameValueCollection;
//import org.dreamwork.util.StringUtil;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.*;

/**
 * Created by IntelliJ IDEA.
 * User: seth
 * Date: 2009-1-4
 * Time: 17:13:48
 */
public class Page extends WebContainer implements IPostbackHandler, ILoadListener {
    private long timestamp;
    private IWebContainer form;
    private boolean postback;
    private PostbackDelegate delegate;
    private ExpressionRunner runner = new ExpressionRunner (this);

    // events
    protected List<ILoadListener> pageLoadEvents = Collections.synchronizedList (new ArrayList<ILoadListener> ());

    protected HttpServletRequest request;
    protected HttpServletResponse response;
    protected HttpSession session;
    protected ServletContext application;
    protected String contentType;
    protected String title;
    protected String docType;
    protected IWebContainer html, head, body;

    protected ClientScriptManager scriptManager = new ClientScriptManager (this);
    protected WebControlCollection directControls = new WebControlCollection ();

    // add by seth.yang on 2012.10.19 for i18n support
    protected String resourceName;
//    protected IResourceAdapter resourceAdapter;
    protected Locale defaultLocale, locale;
    protected Collection<LocaleWarp> supportedLocales;

    public DataContainer container;
    public Stack<DataContainer> stack = new Stack<DataContainer> ();

    private static final Logger logger = Logger.getLogger (Page.class);
    public static final String RESOURCE_MANAGER_KEY = "jasmine.i18n.resource.manager";

    // add by seth.yang on 2013.10.08 for layout support
    protected String layout;
    protected ReplaceHolderControl replaceHolder;

    // add by seth.yang on 2016.09.14 for variable named "_ctx" in javascript
    protected String contextPath;

    public Page () {
        page = this;
        delegate = new PostbackDelegate (this);
    }

    // other functions
    public String getPageCharset () {
        return "UTF-8";
    }

    public String getUniqueId () {
//        throw new IllegalStateException ("Not implements");
        return getClass ().getName ();
    }

    public IWebContainer getForm () {
        return form;
    }

    public IWebContainer getHtml () {
        return html;
    }

    public IWebContainer getHead () {
        return head;
    }

    public IWebContainer getBody () {
        return body;
    }

    public String getTitle () {
        return title;
    }

    public void setTitle (String title) {
        this.title = title;
    }

    public String getContentType () {
        return contentType;
    }

    public void setContentType (String contentType) {
        this.contentType = contentType;
    }
    // end of functions

    // add by seth.yang on 2013.10.08 for layout support
    public String getLayout () {
        return layout;
    }

    public void setLayout (String layout) {
        this.layout = layout;
    }

    public ReplaceHolderControl getReplaceHolder () {
        return replaceHolder;
    }
    // end of add 2013.10.08

    // web application's variables
    public void setHttpContext (HttpContext context) {
        super.setHttpContext (context);
//        context.setPage (this);
        request = context.getRequest ();
        session = context.getSession ();
        response = context.getResponse ();
        application = context.getApplication ();
        // add by seth.yang on 2016.09.14 for _ctx
        contextPath = application.getContextPath ();

        if (replaceHolder != null && replaceHolder.childControls.count () > 0)
            ((Page)replaceHolder.childControls.get (0)).setHttpContext (context);
    }
    // end of web application's variables

    // add by seth.yang on 2012.10.19 for i18n support
    public String getResource () {
        return resourceName;
    }

    public void setResource (String resourceName) {
        this.resourceName = resourceName;
    }

    public Locale getLocale () {
        return locale;
    }

    public void setLocale (Locale locale) {
        this.locale = locale;
        if (replaceHolder != null && replaceHolder.childControls.count () > 0)
            ((Page)replaceHolder.childControls.get (0)).setLocale (locale);
    }

    public Locale getDefaultLocale () {
        return defaultLocale;
    }

    public void setDefaultLocale (Locale defaultLocale) {
        this.defaultLocale = defaultLocale;
        if (replaceHolder != null && replaceHolder.childControls.count () > 0)
            ((Page)replaceHolder.childControls.get (0)).setDefaultLocale (defaultLocale);
    }

    public Collection<LocaleWarp> getSupportedLocales () {
        return supportedLocales;
    }

    public void setSupportedLocales (Collection<LocaleWarp> supportedLocales) {
        this.supportedLocales = supportedLocales;
        if (replaceHolder != null && replaceHolder.childControls.count () > 0)
            ((Page)replaceHolder.childControls.get (0)).setSupportedLocales (supportedLocales);
    }

    /*
        public IResourceAdapter getResourceAdapter () {
            return resourceAdapter;
        }
    */
    // end of i18n support

    // life circle
    public void init () {
        htmlTagName = "";
        timestamp = System.currentTimeMillis ();
        initComponents ();
        for (int i = 0 ; i < directControls.count (); i ++) {
            IWebControl control = directControls.getControl (i);
            if (control instanceof UserControl) {
                ((UserControl) control).setHttpContext (context);
                ((UserControl) control).setLocale (locale);
            }
        }
//        walkControls (this);
        super.init ();
        stateManager.put ("uniqueId", getUniqueId ());
        addPageLoadEvent (this);

/*
        if (locale != null && !StringUtil.isEmpty (resourceName)) {
            IResourceManager manager = (IResourceManager) application.getAttribute (RESOURCE_MANAGER_KEY);
            if (manager != null)
                resourceAdapter = manager.getResourceAdapter (resourceName);
        }
*/
    }

    public void onPagePreload (Page page) throws EventException {}

    public void onPageLoad (Page page) throws EventException { }

    public void onPageLoadCompleted (Page page) throws EventException { }

    public final void preload () throws EventException {
        for (ILoadListener listener : pageLoadEvents) {
            listener.onPagePreload (this);
        }
        for (int i = 0; i < directControls.count (); i ++) {
            IWebControl control = directControls.getControl (i);
            if (control instanceof UserControl)
                ((UserControl) control).preload ();
        }
        if (replaceHolder != null)
            ((UserControl)replaceHolder.getChildControls ().get (0)).preload ();
    }

    public final void load () throws EventException {
        loadViewState ();
        HttpRequestAdapter adapter = new HttpRequestAdapter (request);
        postbackData (adapter.fill ());
        firePageLoadEvents ();
        for (int i = 0; i < directControls.count (); i ++) {
            IWebControl control = directControls.getControl (i);
            if (control instanceof UserControl)
                ((UserControl) control).load ();
        }
        if (replaceHolder != null)
            ((UserControl)replaceHolder.getChildControls ().get (0)).load ();
    }

    public final void loadComplete () throws EventException {
        for (ILoadListener listener : pageLoadEvents) {
            listener.onPageLoadCompleted (this);
        }
        for (int i = 0; i < directControls.count (); i ++) {
            IWebControl control = directControls.getControl (i);
            if (control instanceof UserControl)
                ((UserControl) control).loadComplete ();
        }
        if (replaceHolder != null)
            ((UserControl)replaceHolder.getChildControls ().get (0)).loadComplete ();
    }

    public void postbackData (NameValueCollection props) {
        delegate.postbackData (props);
    }

    public void render (PrintWriter writer) throws IOException, EventException {
        firePreRenderEvents ();
        createEvntStub ();
        addClientScripts ();
        createViewState ();
        super.render (writer);
        writer.println ("<!-- time expried in " + (System.currentTimeMillis () - timestamp) + " millisecond -->");
        writer.println ("<!-- Created by Jasmine 2.0 -->");
    }

    public void dispose () throws Throwable {
        fireDisposeEvents ();
        for (int i = 0; i < childControls.count (); i++) {
            IWebControl control = childControls.getControl (i);
            control.dispose ();
        }
        pageLoadEvents.clear ();
        directControls.clear ();
        childControls.clear ();
        if (stateManager != null) stateManager.clear ();
    }
    // end of life circle

    // events
    public synchronized void addPageLoadEvent (ILoadListener listener) {
        pageLoadEvents.add (listener);
    }

    public synchronized void removePageLoadEvent (ILoadListener listener) {
        pageLoadEvents.remove (listener);
    }

    public synchronized ILoadListener[] getPageLoadEvents () {
        List<ILoadListener> list = new ArrayList<ILoadListener> (pageLoadEvents.size ());
        list.addAll (pageLoadEvents);
        ILoadListener[] ssl = new ILoadListener[list.size ()];
        return list.toArray (ssl);
    }

    public synchronized void firePageLoadEvents () throws EventException {
        ILoadListener[] ssls = getPageLoadEvents ();
        for (ILoadListener ssl : ssls) {
            ssl.onPageLoad (this);
        }
    }

    // end of events
    public boolean isPostback () {
        return postback;
    }

    public IWebControl findControl (String id) {
        for (int i = 0; i < directControls.count (); i++) {
            IWebControl c = directControls.getControl (i);
            if (id.equals (c.getId ())) return c;
            if (id.equals (c.getClientId ())) return c;
        }

        for (int i = 0; i < childControls.count (); i++) {
            IWebControl c = childControls.getControl (i);
            if (id.equals (c.getId ())) return c;
            if (id.equals (c.getClientId ())) return c;
            if (c instanceof IWebContainer) {
                IWebControl control = findControl ((IWebContainer) c, id);
                if (control != null) return control;
            }
        }
        return null;
    }

    /////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // dilegate for ClienScriptManager
    public void registerClientScriptBlock (Class clazz, String script) {
        scriptManager.registerClientScriptBlock (clazz, script);
    }

    public void registerStyleFile (Class clazz, String style) {
        scriptManager.registerStyleFile (clazz, style);
    }

    public void addInitStatement (String name, String statement) {
        scriptManager.addInitStatement (name, statement);
    }

    public void addStartupStatement (String statement) {
        scriptManager.addStartupStatement (statement);
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////

    protected IWebControl findControl (IWebContainer parent, String id) {
        ICollection collection = parent.getChildControls ();
        for (int i = 0; i < collection.count (); i++) {
            IWebControl c = (IWebControl) collection.get (i);
            if (id.equals (c.getId ())) return c;
            if (id.equals (c.getClientId ())) return c;
        }
        for (int i = 0; i < collection.count (); i++) {
            IWebControl c = (IWebControl) collection.get (i);
            if (c instanceof IWebContainer) {
                IWebControl control = findControl ((IWebContainer) c, id);
                if (control != null) return control;
            }
        }
        return null;
    }

    protected Object findDataSource (Object obj) {
        if (obj.getClass () == String.class) {
            return context.findAttribute ((String) obj);
        }
        return obj;
    }

    protected void setServerSideForm (GenericHtmlControl theForm) {
        if (form != null)
            throw new IllegalStateException ("Only ONE server side form allowed!");
        form = theForm;
    }

    protected File getContextTempDir () {
        if (application == null) {
            if (context == null) {
                return null;
            }

            application = context.getApplication ();
            if (application == null) {
                return null;
            }
        }

        return (File) application.getAttribute (JasmineEngine.J2EE_TEMP_DIR);
    }

    public Object eval (String expression) throws Exception {
        return runner.eval (expression);
    }

    protected void initComponents () {
    }

    protected void appendViewState (IWebContainer container, StringBuffer buff) throws IOException {
        for (int i = 0; i < container.getChildControls ().count (); i++) {
            IWebControl c = container.getControl (i);
            if (c != form) {
                if (c instanceof WebControl) {
                    WebControl wc = (WebControl) c;
                    if (wc.isViewStateEnabled ()) {
                        ViewStateManager manager = wc.getViewState ();
                        if (manager != null) {
                            manager.trackViewState ();
                            Object savedViewState = manager.saveViewState ();
                            if (savedViewState != null) {
                                buff.append ("&").append (c.getId ()).append ("=").append (savedViewState);
                                logger.debug ("Control.ID = " + c.getId () + " save view state");
                            }
                            manager.stopTrackViewState ();
                        }
                    }
                }
            }
            if (c instanceof IWebContainer) {
                appendViewState ((IWebContainer) c, buff);
            }
        }
    }

    private void createViewState () throws EventException {
        try {
            if (form != null) {
                StringBuffer buff = new StringBuffer ();
                stateManager.trackViewState ();
                buff.append ("page=").append (stateManager.saveViewState ());
                appendViewState (this, buff);
/*
                BASE64Zipper zipper = new BASE64Zipper ();
                String value = zipper.Base64ZipEncode (buff.toString ().getBytes ());
*/
                String value = new String (Base64.encode (buff.toString ().getBytes ()));
                buff = new StringBuffer ("<input type=\"hidden\" name=\"__viewstate\" value=\"");
                buff.append (value).append ("\"/>\n");
                form.addControl (new Literal (buff));
                form.setAttribute ("action", request.getContextPath () + request.getServletPath ());
                form.setAttribute ("method", "POST");
                stateManager.stopTrackViewState ();
            }
        } catch (Exception ex) {
            logger.error (ex.getMessage (), ex);
            throw new EventException (ex);
        }
    }

    private void createEvntStub () {
        if (form == null) return;
//        StringBuffer buff = new StringBuffer ();
        form.addControl (new Literal ("<INPUT TYPE='HIDDEN' NAME='__event.sender' ID='__event.sender'/>\r\n"));
        form.addControl (new Literal ("<INPUT TYPE='HIDDEN' NAME='__event.type' ID='__event.type'/>\r\n"));
        form.addControl (new Literal ("<INPUT TYPE='HIDDEN' NAME='__event.attr' ID='__event.attr'/>\r\n"));
    }

    private void addClientScripts () {
        if (this instanceof UserControl && page != null) return;
        IWebContainer container = head != null ? head :
                form != null ? form :
                        body != null ? body :
                                html != null ? html : this;
        container.addControl (new Literal (
            "<script type=\"text/javascript\" charset=\"utf-8\">_ctx = \"" + contextPath + "\";</script>"
        ));
        ICollection coll = scriptManager.getClientScriptBlocks ();
        for (int i = 0; i < coll.count (); i++)
            container.addControl (new Literal ((String) coll.get (i)));

        coll = scriptManager.getStyles ();
        for (int i = 0; i < coll.count (); i++)
            container.addControl (new Literal ((String) coll.get (i)));

        StringBuffer buff = new StringBuffer ();
        coll = scriptManager.getInitStatements ();
        if (coll.count () > 0) {
            buff.append ("<script lanuage='javascript' type='text/javascript'>\r\n");
            for (int i = 0; i < coll.count (); i++) {
                String stmt = (String) coll.get (i);
                buff.append (stmt);
                if (!stmt.endsWith (";")) buff.append (';');
                buff.append ("\r\n");
            }
            buff.append ("</script>\r\n");
            container.addControl (new Literal (buff));
        }

        coll = scriptManager.getStateupStatements ();
        if (coll.count () == 0) return;
        String onload_script = body != null ? body.getAttribute ("onload") : null;
        List<String> list = new ArrayList<String> ();
        for (int i = 0; i < coll.count (); i++) {
            list.add ((String)coll.get (i));
        }
        if (onload_script != null) list.add (onload_script);
        buff = new StringBuffer ();

        buff.append ("<script language='javascript' type='text/javascript'>\r\n");
        buff.append ("function __Jasmine_Page_Load () {\r\n");
        for (Object aList : list) {
            buff.append (aList);
        }
        buff.append ("}\r\n");
        buff.append ("</script>\r\n");

        container.addControl (new Literal (buff));
        if (body != null) {
            body.setAttribute ("onload", "__Jasmine_Page_Load ()");
        } else {
            (html == null ? this : html).addControl (
                    new Literal ("<script language='javascript'>__Jasmine_Page_Load();</script>\r\n")
            );
        }
    }

    public boolean validControls (IWebControl control) {
        if (control instanceof IValidatable) {
            if (!((IValidatable) control).fireValidateListeners (control))
                return false;
        }
        if (control instanceof IWebContainer) {
            WebControlCollection wcc = (WebControlCollection) ((IWebContainer) control).getChildControls ();
            for (int i = 0; i < wcc.count (); i++) {
                if (!validControls (wcc.getControl (i)))
                    return false;
            }
        }
        return true;
    }

    private void loadViewState () {
        String viewState = request.getParameter ("__viewstate");
        if (viewState != null) try {
            viewState = viewState.replace (' ', '+');
/*
            BASE64Zipper zipper = new BASE64Zipper ();
            byte[] data = zipper.Base64ZipDecode (viewState);
*/
            byte[] data = Base64.decode (viewState);
            String[] a = new String (data).split ("&");
            for (String item : a) {
                if (item.startsWith ("page=")) {
                    stateManager.loadViewState (item.substring ("page=".length ()));
                    String uniqueId = (String) stateManager.get ("uniqueId");
                    String uid = getUniqueId ();
                    postback = uid.equals (uniqueId);
                } else {
                    int pos = item.indexOf ('=');
                    if (pos != -1) {
                        String controlId = item.substring (0, pos);
                        IWebControl control = findControl (controlId);
                        if (control instanceof WebControl)
                            ((WebControl) control).loadViewState (item.substring (pos + 1));
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace ();
            logger.warn (e.getMessage (), e);
        }
    }

    public void dispatchEvent () throws EventException {
        String eventSender = request.getParameter ("__event.sender");
        String eventType = request.getParameter ("__event.type");
        String eventAttr = request.getParameter ("__event.attr");

        if (eventSender != null) {
            IWebControl control = findControl (eventSender);
            if (control == null) return;
            if ("click".equalsIgnoreCase (eventType) && (control instanceof IClickable)) {
                ((IClickable) control).fireControlClicked (control);
            } else if ("change".equalsIgnoreCase (eventType) && (control instanceof IChangable)) {
                Object oldValue = ((WebControl) control).oldAttributes.get (eventAttr);
                String newValue = request.getParameter (control.getName ());
                ((IChangable) control).fireChange (control, eventAttr, oldValue, newValue);
            }
        }
    }

    private String target;
    private int gotoType;

    public String getTarget () {
        return target;
    }

    public int getGotoType () {
        return gotoType;
    }

    public void redirect (String url) {
        target = url;
        gotoType = 0;
    }

    public void forward (String url) {
        target = url;
        gotoType = 1;
    }
}